/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"
#include <boost/bind.hpp>

/////////////////////////////////////////////////////////////////////////////

Snapshot::Snapshot(int width, int height, HBITMAP image, INT64 timeTaken)
{
	this->width = width;
	this->height = height;
	this->image = image;
	this->timeTaken = timeTaken;
}

Snapshot::~Snapshot()
{
	DeleteObject(image);
}

/////////////////////////////////////////////////////////////////////////////

SnapshotElementState::SnapshotElementState(HWND owner, SnapshotRequest *request)
{
	this->request = request;
	this->lastUpdate = request->getSnapshotTime();
	this->owner = owner;
}

SnapshotElementState::~SnapshotElementState()
{
	request->removeNotifyRequest(owner);
	request->decrRefCount();
}

/////////////////////////////////////////////////////////////////////////////

SnapshotRequest::SnapshotRequest(SnapshotSpec spec)
{
	this->spec = spec;
	this->refCount = 0;
	this->updatePending = false;
	this->updateCancelled = false;
	this->latestSnapshot = NULL;
}

SnapshotRequest::~SnapshotRequest()
{
	sync.lock();
	
	if(latestSnapshot) {
		delete latestSnapshot;
		latestSnapshot = NULL;
	}
	
	sync.unlock();
}

SnapshotSpec SnapshotRequest::getSpec()
{
	return spec;
}

UINT64 SnapshotRequest::getSnapshotTime()
{
	UINT64 ret;
	
	sync.lock();
		if(latestSnapshot)
			ret = latestSnapshot->timeTaken;
		else
			ret = 0;
	sync.unlock();
	
	return ret;
}

Snapshot *SnapshotRequest::lock()
{
	sync.lock();
	return latestSnapshot;
}

void SnapshotRequest::unlock()
{
	sync.unlock();
}

void SnapshotRequest::setSnapshot(Snapshot *snapshot)
{
	sync.lock();
	if(latestSnapshot)
		delete latestSnapshot;
	latestSnapshot = snapshot;
	
	for(set<HWND>::iterator ii=updateNotifyList.begin(); ii!=updateNotifyList.end(); ii++)
		PostMessage(*ii, UMSG_REFRESHLAYOUT, 0, 0);
	
	sync.unlock();
}

void SnapshotRequest::incrRefCount()
{
	sync.lock();
	refCount++;
	sync.unlock();
}

void SnapshotRequest::decrRefCount()
{
	sync.lock();
	refCount--;
	bool cancelMe = (refCount==0);
	sync.unlock();
	
	if(cancelMe)
		snapshotManager->cancelRequest(this);
}

void SnapshotRequest::addNotifyRequest(HWND window)
{
	sync.lock();
	updateNotifyList.insert(window);
	sync.unlock();
}

void SnapshotRequest::removeNotifyRequest(HWND window)
{
	sync.lock();
	updateNotifyList.erase(window);
	sync.unlock();
}

/////////////////////////////////////////////////////////////////////////////

SnapshotElement::SnapshotElement(string prefix)
	:LayoutElement(prefix)
{
	this->useAspect = getConfigBool("UseAspectRatio", true, prefix.c_str());
	if(useAspect) {
		maxAspect = getConfigFloat("MaxAspectRatio", 2.0, prefix.c_str());
	} else {
		maxLength = getConfigInt("MaxLength", 100, prefix.c_str());
	}
}

SnapshotElement::~SnapshotElement()
{
}

ElementSize SnapshotElement::getLength(const LayoutCacheNode *node)
{
	SnapshotElementState *state = (SnapshotElementState*)node->elementData;
	if(!state)
		return ElementSize(0,0);
	
	SnapshotRequest *request = state->request;
	
	Snapshot *snap = request->lock();
	int ret;
	if(snap)
	{
		if(node->context.vertical)
			ret = snap->height;
		else
			ret = snap->width;
	}
	else
	{
		SnapshotLayout *layout = snapshotManager->getTaskSnapLayout(request);
		if(node->context.vertical)
			ret = layout->outputHeight;
		else
			ret = layout->outputWidth;
		delete layout;
	}
	request->unlock();
	
	return ElementSize(ret, ret);
}

void SnapshotElement::draw(HDC drawContext, const LayoutCacheNode *node)
{
	SnapshotElementState *state = (SnapshotElementState*)node->elementData;
	if(!state) return;
	
	SnapshotRequest *request = state->request;
	
	Snapshot *snap = request->lock();
	if(snap)
	{
		HDC painter = CreateCompatibleDC(drawContext);
		HBITMAP oldBmp = (HBITMAP)SelectObject(painter, snap->image);
		
		TransparentBlt(drawContext,
			node->boundingRect.left, node->boundingRect.top,
			snap->width, snap->height,
			painter, 0, 0,
			snap->width, snap->height,
			RGB(255,0,255));
		
		SelectObject(painter, oldBmp);
		DeleteDC(painter);
	}
	request->unlock();
}

bool SnapshotElement::changed(const LayoutCacheNode *node)
{
	SnapshotElementState *state = (SnapshotElementState*)node->elementData;
	
	if(!state)
		return true;
	if(state->request->getSpec() != getSnapshotSpec(node))
		return true;
	if(state->lastUpdate < state->request->getSnapshotTime())
		return true;
	
	return false;
}

void SnapshotElement::update(LayoutCacheNode *node)
{
	SnapshotElementState *state = (SnapshotElementState*)node->elementData;
	SnapshotSpec spec = getSnapshotSpec(node);
	
	// If there's a snapshot request associated with this element but it's not
	// the right size/task/etc., discard it.
	if(state && state->request->getSpec() != spec)
	{
		delete state;
		state = NULL;
		node->elementData = NULL;
	}
	// If there's no snapshot request associated, request one and associate it
	if(!state)
	{
		HWND requestSource = node->context.window->getWindow();
		state = new SnapshotElementState(requestSource, snapshotManager->requestSnapshot(spec, requestSource));
		node->elementData = state;
	}
}

SnapshotSpec SnapshotElement::getSnapshotSpec(const LayoutCacheNode *node)
{
	SIZE size = getDimensions(node);
	
	SnapshotSpec ret;
		WindowData *taskWindow = node->context.getTask();
		ret.task = taskWindow->task;
		ret.vertical = node->context.vertical;
		if(useAspect) {
			ret.maxLength = node->context.thickness * maxAspect;
		} else {
			ret.maxLength = maxLength;
		}
		ret.thickness = node->context.thickness;
	return ret;
}

void SnapshotElement::deleteElementData(LayoutCacheNode *node, void *elementData)
{
	SnapshotElementState *state = (SnapshotElementState*)elementData;
	delete state;
}

SIZE SnapshotElement::getDimensions(const LayoutCacheNode *node)
{
	ElementSize length = getLength(node);
	SIZE ret;
	
	if(node->context.vertical)
	{
		ret.cx = node->context.thickness;
		ret.cy = length.preferred;
	}
	else
	{
		ret.cx = length.preferred;
		ret.cy = node->context.thickness;
	}
	return ret;
}
